home *** CD-ROM | disk | FTP | other *** search
/ Otherware / Otherware_1_SB_Development.iso / mac / developm / source / drgnsmth.cpt / Dragonsmith 1.1 / Base files / Dragon.c < prev    next >
Encoding:
C/C++ Source or Header  |  1992-10-10  |  35.9 KB  |  1,105 lines

  1. /*
  2.     Dragon.c
  3.     
  4.     Simple framework for a drag-and-drop application.  A "dragon" does something meaningful (one
  5.     would hope) with the Finder objects that were drag-and-dropped onto it.  Most dragons will
  6.     stick around after the initial launch unless there are no settings for the user to change.
  7.     
  8.     This class does nothing by itself; simple subclasses will need to override ProcessFile ╤
  9.     more powerful subclasses may need to do much more (e.g., override event handling methods).
  10.     
  11.     Developed using THINK C 5.0 and ResEdit 2.1.1
  12.     
  13.     Copyright ⌐ 1992 by Paul M. Hoffman
  14.     Send comments or suggestions to paul.hoffman@um.cc.umich.edu
  15.     
  16.     This source code may be freely used, altered, and distributed in any way as long as:
  17.         1.    It is GIVEN away rather than sold (except as expressly permitted by the author)
  18.         2.    This statement and the above copyright notice are left intact.
  19.     
  20.     For a complete description of the contents of this file, see the "Dragonsmith Programmer╒s Manual"
  21.     
  22. */
  23.  
  24. #include    "Dragon.h"
  25.  
  26. #include    <Folders.h>
  27. #include    "AppleEventUtils.h"
  28. #include    "EventUtils.h"
  29. #include    "GestaltUtils.h"
  30. #include    "HandleUtils.h"
  31. #include    "MenuUtils.h"
  32. #include    "StringUtils.h"
  33. #include    "ProcessUtils.h"
  34. #include    "TrapUtils.h"
  35.  
  36. //    -------------------------------------------    I n i t i a l i z a t i o n     m e t h o d s    ----------------------------------------------
  37.  
  38. Dragon::Dragon (void)
  39. {
  40.     FSSpec    appFile;
  41.     Boolean    inForeground;
  42.     Handle    h;
  43.     OSErr    err;
  44.     
  45.     // We use unrelocatable blocks for curDocPB and curDocFSS in order to avoid having to HLock the dragon object
  46.     //    to prevent its instance variables from moving during calls to (for example) ResolveAliasFile, etc.
  47.     
  48.     curDocFSS = (FSSpec *) NewPtr (sizeof (FSSpec));
  49.     curDocPB = (PBRecUnion *) NewPtr (sizeof (PBRecUnion));    
  50.     if (curDocPB == NULL || curDocFSS == NULL)
  51.         Abort (memFullErr);
  52.         
  53.     curDocPB->h.fileParam.ioCompletion = NULL;            // All PB calls will be synchronous
  54.     curDocPB->h.fileParam.ioNamePtr = curDocFSS->name;    // This only has to be set once, since curDocFSS won't move
  55.     curDocFSS->name[0] = 0;    // Just in case
  56.  
  57.     dirDepthLimit = 0;            // Change this instance variable to a largish negative number (say, -100) in your
  58.                             //    dragon's constructor if you want to recursively open all folders and volumes to
  59.                             //    process what's inside.  Or change it to -1 if you just want to go down 1 level
  60.     curDirDepth = 0;            // Start out at the top level of processing (of course)
  61.     filesOnly = FALSE;            // If we get FSSpecs to folders to volumes, go ahead and use them as is
  62.                             //    ╤ set this to TRUE if dirDepthLimit contains a small negative value (i.e., a value
  63.                             //    near zero) and your ProcessDroppings is designed to work only on files
  64.                             // If you don't want to get folders and volumes at all, just say "no" ╤ don't put
  65.                             //    'fold' and 'disk' in the FREF
  66.     resolveAliases = TRUE;        // Resolve any aliases you end up getting? (i.e., by opening folders ╤ the Finder
  67.                             //    kindly resolves any aliases that are dragged-and-dropped, so this instance
  68.                             //    variable is meaningless unless dirDepthLimit < 0)
  69.     followAliasChain = TRUE;    // Always resolve aliases to the original file (not to an intermediate alias)?
  70.     useCustomFilter = FALSE;    // Don't do any special filtering of docs
  71.     acceptableTypes = NULL;    // The types of documents which we can process (e.g., 'TEXT', '****', 'disk', etc.)
  72.     
  73.     preferences = NULL;
  74.     prefsFileType = kPrefsFileType;    // Subclasses should probably not change this ╤ the System 7 Finder displays
  75.                                 //    a rather nice icon for files of this type
  76.     dragonPrefs = NULL;
  77.     
  78.     appResFork = CurResFile ();    // Keep a refNum to the application's resource fork
  79.     
  80.     // Now use it to get an FSSpec to our app's file ╤ we use a local copy because calling RefNumToFSSpec 
  81.     //    may move memory
  82.     if (RefNumToFSSpec (appResFork, &appFile, NULL) != noErr)
  83.         appFile.name[0] = 0;        // This should never never be executed, but then you never never know╔
  84.     this->appFile = appFile;            // Copy local variable to instance variable
  85.     
  86.     h = Get1Resource ('BNDL', 128);
  87.     signature = (h ? **((OSType **) h) : '????');        // '????' is the least obnoxious default I could think of
  88.  
  89.     acceptableTypes = FREFTypes (appResFork);    // File types this dragon will accept (from its 'FREF' resources)
  90.  
  91.     // We hope to start in the foreground, but we may not ╤ the Finder launches applications in the foreground, but
  92.     //    somebody else might launch us in the background.  Don't fight it, just grin and bear it.  If something goes
  93.     //    wrong, the safer assumption is that we're in the background
  94.     err = StartAppInForeground (&inForeground);
  95.     if (err != noErr)
  96.         inForeground = FALSE;
  97.         
  98.     // These hard-coded default sleep values will be overridden by ones from the Dragon preferences resource
  99.     //    ('DrPr' 128) ╤ if everything goes well in InitPrefs, that is
  100.     sleepValue[kFGIdle] = 14;                    // Allow TEIdle to work OK (15 would PROBABLY be safe, too)
  101.     sleepValue[kBGIdle] = 60 * 60;                // Laziest state ╤ check once every minute
  102.     sleepValue[kFGBusy] = 3;                    // Give the user the CPU's (almost) undivided attention
  103.     sleepValue[kBGBusy] = 60;                    // Process about one doc per second while in the background
  104.     
  105.     runState = (inForeground ? kFGIdle : kBGIdle);    // We're idle until we get an 'odoc' event
  106.     sleepTime = sleepValue[runState];                // Make sure sleepTime is set correctly
  107.     
  108.     running = TRUE;                            // Setting running = FALSE later will cause the dragon to quit
  109.     abortProcessing = FALSE;                    // Nothing's gone wrong yet
  110.     busyCursor = GetCursor (kWatchCursor);        // Get the watch cursor
  111.     if (busyCursor == NULL)                        // Make sure we actually got it
  112.         Abort (eInitializationFailed);                //    (not that this will ever happen, but╔)
  113.     HLockHi (busyCursor);                        // Lock it down for carefree dereferencing later
  114.     cursorRgn = NULL;                            // This is for WaitNextEvent
  115.  
  116.     aeQueue = NULL;
  117.     numAEsPending = 0;
  118.     
  119.     autoQuit = FALSE;            // A subclass's constructor (which will be called AFTER this one) should set autoQuit
  120.                             //    to TRUE instead if it wants to hang around after starting up ╤ and should set
  121.                             //    the corresponding bit in 'DrPr' 128
  122.     menusInstalled = FALSE;    // No menus yet
  123.     appleMenu = NULL;
  124.     fileMenu = NULL;
  125.     editMenu = NULL;
  126. }
  127.  
  128. void Dragon::Start (void)
  129. {
  130.     InitMem ();
  131.     InitMac ();
  132.     InitMilieu ();
  133.     InitPrefs ();
  134.     if (!autoQuit)                // Don't bother with menus if we're just going to quit right away
  135.         SetUpMenus ();
  136.     FlushEvents (everyEvent, 0);
  137. }
  138.  
  139. void Dragon::InitMem (void)
  140. {    
  141.     // If you want to increase the stack size, right here (before MaxApplZone) is where to do it
  142.     MaxApplZone ();
  143.     CallMoreMasters ();
  144. }
  145.  
  146. void Dragon::CallMoreMasters (void)
  147. {
  148.     short        mmCalls, **mmCallsHandle;
  149.     
  150.     // Call MoreMasters the number of times specified in the 'MoMa' resource ╤ each call gives
  151.     //    us an extra block of master pointers
  152.     mmCallsHandle = (short **) GetResource ('MoMa', 128);
  153.     if (mmCallsHandle != NULL) {
  154.         for (mmCalls = **mmCallsHandle; mmCalls > 0; mmCalls--)
  155.             MoreMasters ();
  156.         HPurge ((Handle) mmCallsHandle);
  157.     }
  158. }
  159.  
  160. void Dragon::InitMac (void)
  161. {
  162.     InitGraf (&thePort);
  163.     InitFonts ();
  164.     InitWindows ();
  165.     InitMenus ();
  166.     TEInit ();
  167.     InitDialogs (NULL);
  168.     InitCursor ();
  169. }
  170.  
  171. void Dragon::InitMilieu (void)
  172. {
  173.     OSErr        err;
  174.     long            result;
  175.     Boolean        hasAppleEvents;
  176.     GstCheckList    **checkList;
  177.     
  178.     if (!TrapAvailable (0xA1AD))        // 0xA1AD == _Gestalt
  179.         Abort (eInsufficientSystem);
  180.     
  181.     checkList = (GstCheckList **) Get1Resource (kGstCheckType, rGstChecklist);
  182.     err = GestaltBatchCheck (checkList);
  183.     if (err != noErr)
  184.         Abort (err);
  185.     
  186.     HPurge ((Handle) checkList);
  187.     InitAppleEvents ();
  188. }
  189.  
  190. void Dragon::InitAppleEvents (void)
  191. {
  192.     OSErr    err;
  193.     
  194.     // Make the Apple event queue object, which stores suspended Apple events for handling later
  195.     aeQueue = MakeAEQObject ();
  196.     if (aeQueue == NULL)
  197.         Abort (memFullErr);
  198.     
  199.     // Install Apple event handler functions ╤ abort if we get a non-zero result from AEInstallEventHandler
  200.     if (AEInstallEventHandler (kCoreEventClass, kAEOpenApplication, HandleOapp, 0, FALSE)
  201.         || AEInstallEventHandler (kCoreEventClass, kAEOpenDocuments, HandleOdoc, 0, FALSE)
  202.         || AEInstallEventHandler (kCoreEventClass, kAEPrintDocuments, HandlePdoc, 0, FALSE)
  203.         || AEInstallEventHandler (kCoreEventClass, kAEQuitApplication, HandleQuit, 0, FALSE)
  204.     )
  205.         Abort (eCouldntInstallAppleEvents);
  206. }
  207.  
  208. AppleEventQueue *Dragon::MakeAEQObject (void)
  209. {
  210.     return new AppleEventQueue;
  211. }
  212.  
  213. //    -------------------------------------------    P r o c e s s i n g     m e t h o d s    ----------------------------------------------
  214.  
  215. OSErr Dragon::ProcessDroppings (AEDescList *docList)
  216. {
  217.     // We've received an 'odoc' event and are ready to process the docs (files, folders and volumes) in the event
  218.     
  219.     // NOTE:    If you are upgrading from Dragonsmith 1.0b2, do NOT override this method! ╤ read the documentation for
  220.     //        instructions (you need to override ProcessFile and/or ProcessDirectory instead)
  221.     
  222.     long            numDocs, i, actualSize;
  223.     Boolean        wasAlias;
  224.     OSErr        err;
  225.     AEKeyword    keyword;
  226.     DescType    returnedType;
  227.     
  228.     err = AECountItems (docList, &numDocs);
  229.     if (err == noErr) {
  230.         BeginProcessing ();
  231.         for (i = 1; i <= numDocs && !abortProcessing; i++) {
  232.             err = AEGetNthPtr (docList, i, typeFSS, &keyword, &returnedType,
  233.                                                         (Ptr) curDocFSS, sizeof (FSSpec), &actualSize);
  234.             if (err == noErr) {
  235.                 err = FSpToPBCatInfo (curDocPB, curDocFSS, resolveAliases, followAliasChain, &wasAlias);
  236.                 if (err == noErr)
  237.                     ProcessDoc ();
  238.             }
  239.         }
  240.         err = noErr;
  241.         EndProcessing ();
  242.     }
  243.     
  244.     return err;
  245. }
  246.  
  247. void Dragon::BeginProcessing (void)
  248. {
  249.     // Override if you need to do anything special before processing any docs ╤ for example, to interact with the user.
  250.     //    Remember, you can always bail out by calling StopProcessing
  251.     
  252.     // NOTE:    Your subclass's method should call inherited::BeginProcessing (or duplicate its functionality)
  253.     
  254.     // Default behavior ╤
  255.     //    1.    Turn on the busy flag in runState and adjust sleepTime accordingly
  256.     //    2.    Disable menus
  257.     //    3.    Put up a busy cursor
  258.     
  259.     curDirDepth = 0;                // We haven't yet delved down into any folders or disks
  260.     abortProcessing = FALSE;        // Nothing's gone wrong yet╔
  261.     
  262.     runState |= maskBusy;
  263.     sleepTime = sleepValue[runState & (maskInBG | maskBusy)];
  264.     if (menusInstalled) {
  265.         AdjustMenusBusy ();
  266.         DrawMenuBar ();            // Disabling an entire menu requires us to call DrawMenuBar afterwards
  267.     }
  268.     CursorBusy ();
  269. }
  270.  
  271. void Dragon::EndProcessing (void)
  272. {
  273.     // Surely someone will think of something else to do here!
  274.  
  275.     // NOTE:    Your subclass's method should call inherited::EndProcessing
  276.     
  277.     // Default behavior ╤
  278.     //    1.    Turn on the busy flag in runState and adjust sleepTime accordingly
  279.     //    2.    Enable menus
  280.     //    3.    Restore the arrow cursor
  281.     
  282.     curDirDepth = 0;                // Reset, just to be cautious ╔
  283.     
  284.     runState &= ~maskBusy;
  285.     sleepTime = sleepValue[runState & (maskInBG | maskBusy)];
  286.     if (menusInstalled) {
  287.         AdjustMenusIdle ();
  288.         DrawMenuBar ();            // Enabling an entire menu requires us to call DrawMenuBar afterwards
  289.     }
  290.     CursorIdle ();
  291. }
  292.  
  293. void Dragon::AdjustMenusBusy (void)
  294. {
  295.     // We're about to start processing docs ╤ disable any menu items that should not be selected (generally speaking,
  296.     //    this means everything but the Apple and Edit menus)
  297.     
  298.     DisableItem (appleMenu, 1);        // Disable the About╔ item
  299.     DisableItem (fileMenu, 0);        // Disable the entire File menu
  300. }
  301.  
  302. void Dragon::AdjustMenusIdle (void)
  303. {
  304.     // Enable menu items that were disabled when we started processing docs
  305.     
  306.     EnableItem (appleMenu, 1);        // Enable the About╔ item
  307.     EnableItem (fileMenu, 0);        // Enable what we disabled in AdjustMenusBusy ╤ this will NOT enable items that
  308.                                 //    were disabled to begin with
  309. }
  310.  
  311. void Dragon::CursorBusy (void)
  312. {
  313.     // Put up a cursor to show the user we're working on it (only if we're in the foreground)
  314.     if (!(runState & maskInBG))
  315.         SetCursor (*busyCursor);
  316. }
  317.  
  318. void Dragon::CursorIdle (void)
  319. {
  320.     // Restore the arrow cursor
  321.     InitCursor ();
  322. }
  323.  
  324. void Dragon::ShowProgress (void)
  325. {
  326.     // This is a good place to let the user know you're working on things ╤ spin a cursor, show a progress bar (hard to
  327.     //    do if your dragon looks inside folders, I'm afraid), etc.
  328. }
  329.  
  330. void Dragon::DoBusy (void)
  331. {
  332.     // Override this to do periodic actions while we're processing docs ╤ it'll be called once before each doc.  This is the
  333.     //    "busy" counterpart to DoIdle
  334.  
  335.     // Default behavior ╤
  336.     //    1.    Call ShowProgress to let the user know we're hard at work
  337.     //    2.    Call WaitNextEvent ╤ yield some time to background processes, let the user cancel the docs processing,
  338.     //            switch to another process, or launch something from the Apple menu (if it exists), etc. Note that this
  339.     //            may result in an Apple event being generated (including 'quit', which lands us in StopRunning)
  340.     
  341.     EventRecord    event;
  342.     Boolean        canAbort;
  343.     
  344.     ShowProgress ();
  345.     
  346.     if (WaitNextEvent (everyEvent, &event, sleepTime, NULL))        // At this point, sleepTime should be a very low value
  347.         DoEvent (&event);
  348.     
  349.     // Note the absence of an else-clause here (as contrasted with the call to WaitNextEvent in the Run method) ╤ we don't
  350.     //    want to call DoIdle here (after all, we're not idle, we're busy!)
  351. }
  352.  
  353. void Dragon::ProcessDoc (void)
  354. {
  355.     // If you override this method, make sure you call CanProcessDoc first and bail out if it returns FALSE
  356.     // You might, for example, want to treat volumes and folders differently ╤ you could add a separate method
  357.     //    ProcessVolume that's called if curDocIsVolume == TRUE
  358.     
  359.     DoBusy ();
  360.     
  361.     // We have to check running and abortProcessing before continuing ╤ DoBusy may call StopRunning
  362.     if (running && !abortProcessing && CanProcessDoc ()) {
  363.         if (curDocIsFile) {
  364.             if (curFileCreator == signature)
  365.                 ProcessOwnedFile ();
  366.             else
  367.                 ProcessFile ();
  368.         } else {
  369.             // It's a directory (either a folder or a volume) ╤ look inside it only if we haven't already gone down as
  370.             //    far as we're allowed (remember, directory levels are zero or negative, never positive)
  371.             if (dirDepthLimit < curDirDepth) {
  372.                 curDirDepth--;        // We're going down a level
  373.                 ProcessDocsInDirectory (curDocPB->h.fileParam.ioVRefNum, curDocPB->h.fileParam.ioDirID);
  374.                 curDirDepth++;    // Pop back up to the level we were at before
  375.             } else if (!filesOnly)
  376.                 ProcessDirectory ();
  377.         }
  378.     }
  379. }
  380.  
  381. Boolean Dragon::CanProcessDoc (void)
  382. {
  383.     OSType    type;
  384.     
  385.     // We have to get a little tricky here ╤ the macro curFileType is only valid for files
  386.     type = curDocIsFile ? curFileType : (curDocIsVolume ? 'disk' : 'fold');
  387.     if (OpenableType (type, acceptableTypes))
  388.         return useCustomFilter ? CustomFilterDoc () : TRUE;
  389.     else
  390.         return FALSE;
  391. }
  392.  
  393. Boolean Dragon::CustomFilterDoc (void)
  394. {
  395.     return TRUE;        // Default is to not filter out anything
  396. }
  397.  
  398. void Dragon::ProcessDocsInDirectory (short vRefNum, long dirID)
  399. {
  400.     // NOTE:    The way things are set up in Dragon, this method is called only by ProcessDoc.  If your dragon calls it from one
  401.     //        of its methods, make sure you set up curDirDepth (and dirDepthLimit) correctly beforehand!
  402.     
  403.     // Be very careful when overriding this method ╤ things are set up here to make sure that the values in
  404.     //    *curDocPB and *curDocFSS are valid any time another method might be called
  405.     
  406.     OSErr        err;
  407.     short        i;
  408.     Boolean        wasAliasFile;
  409.         
  410.     // NOTE:    curDocPB->h.hFileInfo.ioNamePtr was set to curDocFSS->name in Dragon::Dragon
  411.     //        above and would only need to be reset if it was changed at some point later on
  412.     
  413.     // Loop through each file/folder in a directory
  414.     for (i = 1, err = noErr; err != fnfErr && !abortProcessing; i++) {
  415.     
  416.         // The loop ends only when we get an fnfErr ╤ any other error simply causes us to skip to the next iteration
  417.         //    (i.e., the next file/folder in the given folder/volume)
  418.         
  419.         // Set up the three fields that together identify the file or folder we want information about ╤ they're
  420.         //    liable to have changed since the last time we were at this point in the loop
  421.         curDocPB->h.fileParam.ioVRefNum = vRefNum;
  422.         curDocPB->h.fileParam.ioDirID = dirID;
  423.         curDocPB->h.fileParam.ioFDirIndex = i;
  424.         
  425.         // Convert curDocPB to curDocFSS, filling in curDocPB and (if appropriate) resolving aliases along the way
  426.         err = PBToFSpCatInfo (curDocPB, curDocFSS, resolveAliases, followAliasChain, &wasAliasFile);
  427.         if (err == noErr)
  428.             ProcessDoc ();
  429.     }
  430.     
  431.     // According to Tech Note #68: Searching All Directories on an HFS Volume, "╔ stopp[ing] the whole search when
  432.     //    we [encounter] an error ╔ cause[s] trouble with privilege errors on AppleShare volumes"
  433.     // This would seem to indicate we have to set abortProcessing = FALSE here (in case it was set to TRUE somewhere
  434.     //    along the line) ╤ but it doesn't make sense to me, I don't like it, and I won't do it!
  435. }
  436.  
  437. void Dragon::ProcessFile (void)
  438. {
  439.     // NOTE:    This method, if overridden, can do ANYTHING to *curDocFSS and *curDocPB, UNLESS you've
  440.     //        done some indiscreet overriding of Dragon::ProcessDroppings or Dragon::ProcessDocsInDirectory
  441.     //        You do NOT have to restore any values in *curDocFSS or *curDocPB!!!
  442.  
  443.     // Of course, you will normally override this method
  444. }
  445.  
  446. void Dragon::ProcessDirectory (void)
  447. {
  448.     // NOTE:    This method, if overridden, can do ANYTHING to *curDocFSS and *curDocPB, UNLESS you've
  449.     //        done some indiscreet overriding of Dragon::ProcessDroppings or Dragon::ProcessDocsInDirectory
  450.     //        You do NOT have to restore any values in *curDocFSS or *curDocPB!!!
  451.  
  452.     // The directory ID of the folder or volume we're processing is in the .ioDirID field
  453. }
  454.  
  455. void Dragon::ProcessOwnedFile (void)
  456. {
  457.     // NOTE:    This method will be called whenever a file created by this dragon is dropped on it or double-clicked.
  458.     //        This is a handy way to change preferences quickly (if one is sneaky, it can be done in the middle of a
  459.     //        dropped batch)
  460.     
  461.     if (curFileType == prefsFileType)
  462.         if (preferences->UseFile (curDocFSS))
  463.             ReadPrefs ();
  464. }
  465.  
  466. void Dragon::StopProcessing (OSErr err)
  467. {
  468.     // Your class should override this method if it wants to beep, display an error message, etc. ╤ just make sure your
  469.     //    method calls inherited::StopProcessing
  470.     
  471.     abortProcessing = TRUE;
  472. }
  473.  
  474. void Dragon::SaveDocInfo (Boolean refreshFinder)
  475. {
  476.     /* Call this method right after you change one or more of the following values ╤
  477.     
  478.                 curFileType    curFileCreator        curFileFlags    curDocCreated    curDocModified
  479.                 
  480.     or any other field in *curDocPB that PBSetCatInfo takes as input.  To rename the current document, you have to call
  481.     PBHRename rather than just changing *curDocName
  482.     
  483.     WARNING:    If your dragon makes any low-level calls (PB____) that change any other values in *curDocPB that
  484.                 PBSetCatInfo takes as input, then it should do so AFTER calling this method, or save and restore
  485.                 those values, or refrain from calling UpdateCatInfo at all.  For a PARTIAL list of low-level File Manager
  486.                 calls that leave garbage in fields that PBSetCatInfo uses, see the Dragonsmith Programmer's Manual
  487.     */
  488.  
  489.     long        ioDirIDWas;
  490.     short    ioFDirIndexWas;
  491.     OSErr    err;
  492.     
  493.     // Save field values
  494.     ioDirIDWas = curDocPB->c.hFileInfo.ioDirID;        // File number or directory ID, returned by PBGetCatInfo
  495.     ioFDirIndexWas = curDocPB->c.hFileInfo.ioFDirIndex;        
  496.     
  497.     // Give them correct values for PBSetCatInfo
  498.     curDocPB->c.hFileInfo.ioDirID = curDocPB->c.hFileInfo.ioFlParID;
  499.     curDocPB->c.hFileInfo.ioFDirIndex = 0;
  500.     
  501.     // Call PBSetCatInfo for the current document
  502.     err = PBSetCatInfoSync (curDocPB);
  503.     
  504.     // Restore the original values
  505.     curDocPB->c.hFileInfo.ioDirID = ioDirIDWas;
  506.     curDocPB->c.hFileInfo.ioFDirIndex = ioFDirIndexWas;
  507.     
  508.     // Refresh the Finder's display of the current document, if desired ╤ we use the FSpRefreshFinderDisplay
  509.     //    function from FileUtils.c rather than duplicating its functionality here
  510.  
  511.     if (refreshFinder && err == noErr)
  512.         FSpRefreshFinderDisplay (curDocFSS);
  513. }
  514.  
  515. //    -------------------------------------------    E v e n t s     m e t h o d s    ----------------------------------------------
  516.  
  517. void Dragon::Run (void)
  518. {
  519.     EventRecord    event;
  520.     
  521.     while (running) {
  522.         if (WaitNextEvent (everyEvent, &event, sleepTime, NULL))
  523.             DoEvent (&event);
  524.         else if (numAEsPending > 0)        // This code won't get called if we're busy, so we don't
  525.             ResumeAEvent ();            //    need to check for runState & maskBusy here
  526.         else
  527.             DoIdle ();
  528.     }
  529. }
  530.  
  531. void Dragon::DoEvent (EventRecord *event)
  532. {
  533.     switch (event->what) {
  534.         case mouseDown:
  535.             DoMouseDown (event);
  536.             break;
  537.         case mouseUp:
  538.             DoMouseUp (event);
  539.             break;
  540.         case keyDown:
  541.         case autoKey:
  542.             DoKeyDown (event);
  543.             break;
  544.         case activateEvt:
  545.             if (event->modifiers & activeFlag)
  546.                 DoActivate (event);
  547.             else
  548.                 DoDeactivate (event);
  549.             break;
  550.         case updateEvt:
  551.             DoUpdateEvent (event);
  552.             break;
  553.         case diskEvt:
  554.             DoDiskInsert (event);
  555.             break;
  556.         case osEvt:                // Suspend, resume, and mouse-moved events
  557.             DoOSEvent (event);
  558.             break;
  559.         case kHighLevelEvent:
  560.             DoHighLevelEvent (event);
  561.             break;
  562.     }
  563. }
  564.  
  565. void Dragon::DoMouseDown (EventRecord *theEvent)
  566. {
  567.     WindowPtr    whichWindow;
  568.     short        domain;
  569.     long            menuItemCode;
  570.     
  571.     domain = FindWindow (theEvent->where, &whichWindow);
  572.     switch (domain) {
  573.         case inSysWindow:
  574.             SystemClick (theEvent, whichWindow);
  575.             break;
  576.         case inMenuBar:
  577.             // Watch out for stray mouseDowns in non-existent menus ╔
  578.             if (menusInstalled) {
  579.                 menuItemCode = MenuSelect (theEvent->where);
  580.                 if (menuItemCode > 0x0000FFFF) {
  581.                     DoMenu (menuItemCode);
  582.                     ShowMenuAction ();
  583.                 }
  584.             }
  585.             break;
  586.         default:
  587.             break;
  588.     }
  589. }
  590.  
  591. void Dragon::DoMouseUp (EventRecord *theEvent)
  592. {
  593.     // Do nothing
  594. }
  595.  
  596. void Dragon::DoKeyDown (EventRecord *theEvent)
  597. {
  598.     long        menuItemCode;
  599.     
  600.     if (runState & maskBusy && IsCancelEvent (theEvent))
  601.         StopProcessing (userCanceledErr);
  602.     else if ((theEvent->modifiers & cmdKey) && menusInstalled) {        // If we have menus, handle
  603.         menuItemCode = MenuKey (theEvent->message);            //     cmd-key combos
  604.         if (menuItemCode > 0x0000FFFF) {                            // The high word of menuItemCode will be
  605.             DoMenu (menuItemCode);                            //    zero (and the low word undefined) if
  606.             ShowMenuAction ();                                //    no menu item was chosen
  607.         }
  608.     }
  609. }
  610.  
  611. void Dragon::DoActivate (EventRecord *theEvent)
  612. {
  613.     // Null method ╤ override if you have windows
  614. }
  615.  
  616. void Dragon::DoDeactivate (EventRecord *theEvent)
  617. {
  618.     // Null method ╤ override if you have windows
  619. }
  620.  
  621. void Dragon::DoUpdateEvent (EventRecord *theEvent)
  622. {
  623.     // Null method ╤ override if you have windows
  624. }
  625.  
  626. void Dragon::DoDiskInsert (EventRecord *theEvent)
  627. {
  628.     Point    pt = {100, 100};        // Would { -1, -1 } give us a centered dialog??
  629.     
  630.     if ((short) (theEvent->message >> 16) != noErr)        // If the disk isn't formatted (or has problems),
  631.         (void) DIBadMount (pt, theEvent->message);    //     give the user the opportunity to initialize it
  632. }
  633.  
  634. void Dragon::DoOSEvent (EventRecord *theEvent)
  635. {
  636.     switch ((theEvent->message >> 24) & 0x00FF) {        // High byte tells us what kind of event it is
  637.         case suspendResumeMessage:            
  638.             if (theEvent->message & resumeFlag)
  639.                 DoResume ();
  640.             else
  641.                 DoSuspend ();
  642.             break;
  643.         case mouseMovedMessage:
  644.             break;
  645.         default:
  646.             break;
  647.     }
  648. }
  649.  
  650. void Dragon::DoHighLevelEvent (EventRecord *theEvent)
  651. {
  652.     OSErr    err;
  653.     
  654.     // Don't assume all high level events are Apple Events
  655.     if (((HLEventPtr) theEvent)->eventClass == kCoreEventClass)
  656.         err = AEProcessAppleEvent (theEvent);
  657.     else
  658.         DoOtherHLEvent (theEvent);
  659.  
  660.             //  NOTE:    When running in the THINK C Debugger, you have to be careful not to set any
  661.             //        breakpoints between the call to WaitNextEvent and the call to AEProcessAppleEvent
  662.             //        which dispatches to the Apple event handlers (HandleOdoc etc.).  If you forget this,
  663.             //        the call to AEProcessAppleEvent will return noOutstandingHLE (== -608)
  664.  
  665. }
  666.  
  667. void Dragon::DoOtherHLEvent (EventRecord *theEvent)
  668. {
  669.     // Subclasses may want to handle other high-level events
  670. }
  671.  
  672. void Dragon::DoSuspend (void)
  673. {
  674.     // Turn on the in-background bit in runState and adjust sleepTime accordingly
  675.     runState |= maskInBG;
  676.     sleepTime = sleepValue[runState & (maskInBG | maskBusy)];
  677.     
  678.     // If we're busy, stop the busy cursor (boring watch, spinning cursor, back-flipping dogcow, whatever)
  679.     if (runState & maskBusy)
  680.         CursorIdle ();
  681. }
  682.  
  683. void Dragon::DoResume (void)
  684. {
  685.     // Turn off the in-background bit in runState and adjust sleepTime accordingly
  686.     runState &= ~maskInBG;
  687.     sleepTime = sleepValue[runState & (maskInBG | maskBusy)];
  688.     
  689.     // If we're busy, put the "busy" cursor back up
  690.     if (runState & maskBusy)
  691.         CursorBusy ();
  692. }
  693.  
  694. void Dragon::DoIdle (void)
  695. {
  696.     // Override this method to do periodic actions while we're sitting around waiting for something to happen.  This is the
  697.     //    "idle" counterpart to DoBusy (which is called for each doc we try to process) ╤ it'll never be called while we're
  698.     //    processing docs
  699. }
  700.  
  701. void Dragon::StopRunning (OSErr err)
  702. {
  703.     if (runState & maskBusy)            // If we're processing docs, we have to set abortProcessing = TRUE or
  704.         StopProcessing (eForcedQuit);    //    else we'll blithely continue to process 'em (bad idea!)
  705.     running = FALSE;
  706. }
  707.  
  708. //    -------------------------------------------    M e n u     m e t h o d s    ----------------------------------------------
  709.  
  710. void Dragon::SetUpMenus (void)
  711. {
  712.     // NOTE:    Your sub-class of Dragon probably won't need to call SetUpMenus from "outside" (meaning anywhere
  713.     //        but in an overridden SetUpMenus method).  If it does, make sure you DON'T call it unless menusInstalled
  714.     //        == FALSE.  Otherwise, you'll have extra menus (or a cute little picture of a bomb╔)
  715.     
  716.     // Override this method if you have any menus in addition to the Apple, File, and Edit menus
  717.     // Make sure your method calls inherited::SetUpMenus at the beginning and DrawMenuBar at the end, so that ╤
  718.     //    1.    Dragon's menus get initialized
  719.     //    2.    The menu bar gets drawn
  720.  
  721.     appleMenu = GetMenu (mApple);        // Standard Apple Menu
  722.     AddResMenu (appleMenu, 'DRVR');
  723.     InsertMenu (appleMenu, 0);
  724.     
  725.     fileMenu = GetMenu (mFile);
  726.     InsertMenu (fileMenu, 0);
  727.     
  728.     editMenu = GetMenu (mEdit);
  729.     InsertMenu (editMenu, 0);
  730.     
  731.     DrawMenuBar ();
  732.     menusInstalled = TRUE;
  733. }
  734.  
  735. void Dragon::DoMenu (long menuItemCode)
  736. {
  737.     // Override this method if you need more than just the Apple, File, and Edit menus
  738.     
  739.     short    menuID, itemNum;
  740.  
  741.     menuID = menuItemCode >> 16;
  742.     itemNum = menuItemCode & 0xFFFF;
  743.  
  744.     switch (menuID) {
  745.         case mApple:
  746.             DoAppleMenu (itemNum);
  747.             break;
  748.         case mFile:
  749.             DoFileMenu (itemNum);
  750.             break;
  751.         case mEdit:
  752.             DoEditMenu (itemNum);
  753.             break;
  754.         default:
  755.             break;
  756.     }
  757. }
  758.  
  759. void Dragon::DoAppleMenu (short itemNum)
  760. {
  761.     // Implement standard Apple menu functionality
  762.     
  763.     Str255    itemStr;
  764.     
  765.     if (itemNum == iAbout)
  766.         DoAbout ();
  767.     else {
  768.         GetItem (appleMenu, itemNum, itemStr);
  769.         OpenDeskAcc (itemStr);
  770.     }
  771. }
  772.  
  773. void Dragon::DoAbout (void)
  774. {
  775.     // Override this method if you want an About╔ window
  776. }
  777.  
  778. void Dragon::DoFileMenu (short itemNum)
  779. {
  780.     if (itemNum == CountMItems (fileMenu))        // This will work fine no matter how many items you have
  781.         StopRunning (eNormalQuit);            //    in your File menu, as long as Quit is at the end
  782. }
  783.  
  784. void Dragon::DoEditMenu (short itemNum)
  785. {
  786.     // SystemEdit does undo, cut, copy, paste, and clear for us
  787.     if (itemNum <= 6)
  788.         (void) SystemEdit (itemNum - 1);
  789. }
  790.  
  791. void Dragon::Finish (void)
  792. {
  793.     if (preferences != NULL)
  794.         delete preferences;
  795.         
  796.     FlushAEventQueue ();
  797.     delete aeQueue;
  798.     
  799.     /* What else is there to do?
  800.         Release any temporary memory
  801.         Delete any temporary files
  802.         Remove any trap patches
  803.         etc. (look at CApplication.c in the THINK Class Library for some ideas)
  804.     */
  805. }
  806.  
  807. OSErr Dragon::DoOapp (AppleEvent *theAppleEvent, AppleEvent *theReply, long refcon)
  808. {
  809.     // Override this method if you want your dragon to do something special when it's launched
  810.     //    by double-click in the Finder (like put up a status window, start a directory scan, etc.)
  811.     
  812.     if (autoQuit)            // Should we quit immediately?
  813.         StopRunning (eNormalQuit);
  814.  
  815.     return noErr;
  816. }
  817.  
  818. OSErr Dragon::DoOdoc (AppleEvent *theAppleEvent, AppleEvent *theReply, long refcon)
  819. {
  820.     // You should't need to override this method ╤ it calls ProcessDroppings with info gleaned from the 'odoc' event
  821.     
  822.     register OSErr        err;
  823.     AEDescList        docList;
  824.     
  825.     // If we're already processing an Apple event, call SuspendAEvent instead
  826.     if (runState & maskBusy)
  827.         return SuspendAEvent (theAppleEvent, theReply, &HandleOdoc, refcon);
  828.     
  829.     err = AEGetParamDesc (theAppleEvent, keyDirectObject, typeAEList, &docList);
  830.     if (err == noErr) {
  831.         err = GotRequiredParams (theAppleEvent);
  832.         if (err == noErr)
  833.             err = ProcessDroppings (&docList);
  834.         (void) AEDisposeDesc (&docList);
  835.     }
  836.         
  837.     if (autoQuit)                // Should we quit immediately after handling this event?
  838.         StopRunning (eNormalQuit);
  839.         
  840.     return err;
  841. }
  842.  
  843. OSErr Dragon::SuspendAEvent (AppleEvent *event, AppleEvent *reply, AEHandlerFunc *handler, long refcon)
  844. {
  845.     // Postpone processing of the current Apple event till we're done with the current one (and any previously
  846.     //    postponed ones)
  847.     
  848.     OSErr    err;
  849.     
  850.     err = aeQueue->Put (event, reply, handler, refcon);
  851.     if (err == noErr) {
  852.         numAEsPending++;
  853.         err = AESuspendTheCurrentEvent (event);
  854.     }
  855.     
  856.     return err;
  857. }
  858.  
  859. void Dragon::ResumeAEvent (void)
  860. {
  861.     AppleEvent        event, reply;
  862.     AEHandlerFunc    handler;
  863.     long                refcon;
  864.     OSErr            err;
  865.     
  866.     err = aeQueue->Get (&event, &reply, &handler, &refcon);
  867.     if (err == noErr && handler != NULL)
  868.         err = AEResumeTheCurrentEvent (&event, &reply, handler, refcon);
  869.         
  870.     if (--numAEsPending < 0)
  871.         numAEsPending = 0;
  872. }
  873.  
  874. void Dragon::FlushAEventQueue (void)
  875. {
  876.     AppleEvent        event, reply;
  877.     AEHandlerFunc    handler;
  878.     long                refcon;
  879.     OSErr            err;
  880.     
  881.     for (err = noErr; err != noOutstandingHLE; ) {
  882.         err = aeQueue->Get (&event, &reply, &handler, &refcon);
  883.         if (err == noErr && handler != NULL)
  884.             // "Resume" here means we call ReturnEventNotHandled, which just returns errAEEventNotHandled
  885.             (void) AEResumeTheCurrentEvent (&event, &reply, &ReturnEventNotHandled, refcon);
  886.     }
  887. }
  888.  
  889. OSErr Dragon::DoPdoc (AppleEvent *theAppleEvent, AppleEvent *theReply, long refcon)
  890. {
  891.     if (autoQuit)                        // Should we quit immediately after handling this event?
  892.         StopRunning (eNormalQuit);
  893.         
  894.     return errAEEventNotHandled;        // Most subclasses won't need to override this method
  895. }
  896.  
  897. OSErr Dragon::DoQuit (AppleEvent *theAppleEvent, AppleEvent *theReply, long refcon)
  898. {
  899.     StopRunning (eNormalQuit);            // This'll keep us from going through the main event loop again
  900.     return noErr;
  901. }
  902.  
  903. //    -------------------------------------------    P r e f e r e n c e s     m e t h o d s    ----------------------------------------------
  904.  
  905. void Dragon::InitPrefs (void)
  906. {
  907.     FSSpec    prefsFile;
  908.     
  909.     // NOTE:    The application's resource fork must be the most-recently-used one at this
  910.     //        point ╤ you won't have to worry about this unless you do some heavy-duty
  911.     //        overriding of Dragon methods (::InitPrefs, ::Start, ::Dragon)
  912.     // TIP:    Always treat Get1Resource and Count1Resources (etc.) with great respect!
  913.     
  914.     preferences = MakePrefsObject ();
  915.     if (preferences == NULL)
  916.         Abort (memFullErr);
  917.     else {
  918.         if (!preferences->Init (appResFork, signature, prefsFileType))
  919.             Abort (eInitializationFailed);                // Abort if preferences initialization failed completely
  920.         if (FindPrefsFile (&prefsFile))
  921.             (void) preferences->UseFile (&prefsFile);    // Ignore the returned value ╤ we'll call ReadPrefs regardless
  922.         ReadPrefs ();
  923.     }
  924. }
  925.  
  926. void Dragon::ReadPrefs (void)
  927. {
  928.     // Read in any needed prefs resources
  929.     // If your subclass overrides this method, it must call inherited::ReadPrefs
  930.     // When overriding, check for NULL handles ╤ do the same in any other method that might rely on a prefs
  931.     //    resource being in memory.  A screwed-up preferences file should never cause big problems (i.e., crash).
  932.     //    You'll notice that this is the only method in this file that uses the dragonPrefs instance variable ╤ the only
  933.     //    reason it's provided as an instance variable is so that a subclass can change the Dragon preferences in
  934.     //    the preferences files it uses
  935.  
  936.     // Note that you should NOT reference any preferences resource handles that may have been in memory
  937.     //    ╤ they will not be valid handles now because the file they were from has by this time been closed
  938.     
  939.     short    flags, i;
  940.  
  941.     if (dragonPrefs != NULL)
  942.         preferences->ReleasePrefResource (prefDragonPrefs);
  943.         
  944.     dragonPrefs = (DragonPrefsRec **) preferences->GetPrefResource (prefDragonPrefs);
  945.     if (dragonPrefs != NULL) {
  946.         // Read Boolean settings from the miscFlags field of the Dragon preferences resource
  947.         flags = (*dragonPrefs)->miscFlags;
  948.         filesOnly = flags & maskFilesOnly;
  949.         resolveAliases = flags & maskResolveAliases;
  950.         followAliasChain = flags & maskFollowAliasChain;
  951.         autoQuit = flags & maskAutoQuit;
  952.         
  953.         // Read the 4 possible sleepTime values from the Dragon preferences resource
  954.         for (i = 0; i <= 3; i++)
  955.             sleepValue[i] = (*dragonPrefs)->sleep[i];
  956.         sleepTime = sleepValue[runState & (maskInBG | maskBusy)];
  957.         
  958.         // Read dirDepthLimit from the Dragon preferences resource
  959.         dirDepthLimit = (*dragonPrefs)->depthLim;
  960.     }
  961. }
  962.  
  963. Preferences *Dragon::MakePrefsObject (void)
  964. {
  965.     // Override this method if you write your own subclass of Preferences and want to use it instead
  966.     return new Preferences;
  967. }
  968.  
  969. Boolean Dragon::FindPrefsFile (FSSpec *fss)
  970. {
  971.     OSErr            err;
  972.     Str63            fileName;
  973.     unsigned short    len;
  974.     Handle            h = NULL;
  975.     short            saveResFork;
  976.     
  977.     saveResFork = CurResFile ();
  978.     UseResFile (appResFork);
  979.     h = Get1Resource ('STR ', rPrefsFileName);
  980.     UseResFile (saveResFork);
  981.     if (h != NULL) {
  982.         len = **((unsigned char **) h);
  983.         if (len != 0 && len <= 63) {    
  984.             HLock (h);
  985.             CopyPStr ((unsigned char *) *h, fss->name);
  986.             HUnlock (h);
  987.             HPurge (h);
  988.     
  989.             fss->vRefNum = appFile.vRefNum;                    // First look in the directory that the application is in
  990.             fss->parID = appFile.parID;
  991.             if (IsPrefsFile (fss))
  992.                 return TRUE;
  993.                 
  994.             err = FSpFindFolder (kPreferencesFolderType, fss);    // Then look in the Preferences folder
  995.             if (IsPrefsFile (fss))                                // If there's a preferences file there,
  996.                 return TRUE;                                //    then we're done
  997.             else                                            // Otherwise,
  998.                 return MakePrefsFile (fss);                    //    we create one there
  999.         }
  1000.     }
  1001.     
  1002.     if (h != NULL)
  1003.         HPurge (h);
  1004.     
  1005.     return FALSE;            // If we encountered any problems, then we'll rely on the app file for our preferences resources
  1006. }
  1007.  
  1008. Boolean Dragon::MakePrefsFile (FSSpec *fss)
  1009. {
  1010.     OSErr    err;
  1011.     
  1012.     FSpCreateResFile (fss, signature, prefsFileType, smSystemScript);
  1013.     return (ResError () == noErr);
  1014. }
  1015.  
  1016. Boolean Dragon::IsPrefsFile (FSSpec *fss)
  1017. {
  1018.     // This method returns TRUE if the file designated by fss is a preferences file for this dragon
  1019.     // NOTE:    IsPrefsFile may alter *fss, since we tell FSpToPBCatInfo to resolve aliases.  This is a feature, not a bug!
  1020.  
  1021.     OSErr        err;
  1022.     Boolean        isFolder, wasAliasFile;
  1023.     PBRecUnion    pb;
  1024.     Str63        name;
  1025.     
  1026.     pb.c.hFileInfo.ioCompletion = NULL;
  1027.     pb.c.hFileInfo.ioNamePtr = (StringPtr) &name;
  1028.     
  1029.     err = FSpToPBCatInfo (&pb, fss, TRUE, TRUE, &wasAliasFile);
  1030.     return (err == noErr && PBIsFile (&pb) && PBFileCreator (&pb) == signature && PBFileType (&pb) == prefsFileType);
  1031. }
  1032.  
  1033. //    -------------------------------------------    M i s c e l l a n e o u s    ----------------------------------------------
  1034.  
  1035. Boolean Dragon::InteractWithUser (long timeOut)
  1036. {
  1037.     // Call this method any time during the handling of an Apple event (I don't know what will happen
  1038.     //    if there's no current Apple event) if you need to come to the foreground (to pose a dialog, etc.)
  1039.     
  1040.     return AEInteractWithUser (timeOut, NULL, &CallWaitIdle) == noErr;
  1041. }
  1042.  
  1043. Boolean Dragon::WaitIdle (EventRecord *theEvent, long *sleep, RgnHandle *mouseRgn)
  1044. {
  1045.     // This method is called whenever we get an event while AEInteractWithUser is in process
  1046.     // The system will give us only null, OS, activate, and deactivate events
  1047.     
  1048.     *sleep = sleepTime;            // These two assignments tell AEInteractWithUser what to
  1049.     *mouseRgn = cursorRgn;    //    pass to WaitNextEvent on our behalf
  1050.     
  1051.     DoEvent (theEvent);
  1052.     
  1053.     return FALSE;                // Don't abort AEInteractWithUser unless it times out
  1054. }
  1055.  
  1056. void Dragon::Abort (short errNum)
  1057. {
  1058.     Error (errNum);
  1059.     ExitToShell ();
  1060. }
  1061.  
  1062. void Dragon::Error (short errNum)
  1063. {
  1064.     Str255    string;
  1065.     
  1066.     NumToString (errNum, &string);
  1067.     ParamText (&string, "\p", "\p", "\p");
  1068.     Alert (rErrorAlert, NULL);
  1069. }
  1070.  
  1071. //    -------------------------------------------    F u n c t i o n s            ----------------------------------------------
  1072.  
  1073. pascal OSErr HandleOapp (AppleEvent *theAppleEvent, AppleEvent *theReply, long refcon)
  1074. {
  1075.     return gDragon->DoOapp (theAppleEvent, theReply, refcon);
  1076. }
  1077.  
  1078. pascal OSErr HandleOdoc (AppleEvent *theAppleEvent, AppleEvent *theReply, long refcon)
  1079. {
  1080.     return gDragon->DoOdoc (theAppleEvent, theReply, refcon);
  1081. }
  1082.  
  1083. pascal OSErr HandlePdoc (AppleEvent *theAppleEvent, AppleEvent *theReply, long refcon)
  1084. {
  1085.     return gDragon->DoPdoc (theAppleEvent, theReply, refcon);
  1086. }
  1087.  
  1088. pascal OSErr HandleQuit (AppleEvent *theAppleEvent, AppleEvent *theReply, long refcon)
  1089. {
  1090.     return gDragon->DoQuit (theAppleEvent, theReply, refcon);
  1091. }
  1092.  
  1093. pascal OSErr ReturnEventNotHandled (AppleEvent *theAppleEvent, AppleEvent *theReply, long refcon)
  1094. {
  1095.     return errAEEventNotHandled;
  1096. }
  1097.  
  1098. pascal Boolean CallWaitIdle (EventRecord *theEvent, long *sleep, RgnHandle *mouseRgn)
  1099. {
  1100.     // Idle function to be used as parameter to AEInteractWithUser
  1101.     
  1102.     return gDragon->WaitIdle (theEvent, sleep, mouseRgn);
  1103. }
  1104.  
  1105.